package gramaticaPrecedencia;

import gramatica.Gramatica;
import gramatica.ParteDerecha;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;

import simbolo.NoTerminal;
import simbolo.Par;
import simbolo.Pares;
import simbolo.Simbolo;
import simbolo.Terminal;

public class GramaticaPrecedenciaSimple {

	private Gramatica gramatica;

	private Map<Simbolo, Set<Simbolo>> tablaPeMas;

	private Map<Simbolo, Set<Simbolo>> tablaUMas;

	private Map<Simbolo, Set<Simbolo>> tablaPeEstrella;

	private Map<NoTerminal, Set<Simbolo>> comienzos;

	private Map<NoTerminal, Set<Simbolo>> finales;
	
	private Tabla tabla;
	
	private final Simbolo pesos= new Simbolo('$');

	public GramaticaPrecedenciaSimple(Gramatica gramatica) {

		this.setGramatica(gramatica);		

		tablaPeMas = new TreeMap<Simbolo, Set<Simbolo>>();

		tablaUMas = new TreeMap<Simbolo, Set<Simbolo>>();

		tablaPeEstrella = new TreeMap<Simbolo, Set<Simbolo>>();

		comienzos = new TreeMap<NoTerminal, Set<Simbolo>>();

		finales = new TreeMap<NoTerminal, Set<Simbolo>>();
	}
	public boolean Serializar(String path){
    	GramaticaSerializable grama = new GramaticaSerializable(this.getTabla(), this.getGramatica().getSimboloInicial(), this.getGramatica().getProducciones(), this.gramatica.getTerminales());
		boolean exito=true;
    	try{
			exito = grama.serializar(path);
		}catch(IOException exception){
			System.out.println("Error al serializar el archivo.");
		}
		   
		return exito;
	}
	
//es IMPORTANTISIMO armar p+ antes que p*
	public void armarTabla() throws ConflictoTabla {
		
		// Armo la tabla de comienzo y fines
		tablaExtremos();
		// Calculo p+, u+consecutivo
		// No terminales:
		ArrayList<NoTerminal> noTerminales = getGramatica().getNoTerminales();
		for (NoTerminal simboloNT : noTerminales) {
			getTablaPeMas().put(simboloNT, calcularXMas(simboloNT, comienzos));
			getTablaUMas().put(simboloNT, calcularXMas(simboloNT, finales));
		}
		// Terminales:
		ArrayList<Terminal> terminales = getGramatica().getTerminales();
		for (Terminal terminal : terminales) {
			getTablaPeMas().put(terminal, new TreeSet<Simbolo>());
			getTablaUMas().put(terminal, new TreeSet<Simbolo>());
		}
		
		//Calculo p*
		// No Terminales:
		for(Simbolo simbolo : noTerminales){
			getTablaPeEstrella().put(simbolo, calcularPeEstrella(simbolo));
		}
		// Terminales:
		for (Terminal terminal : terminales) {
			TreeSet<Simbolo> peEstrella = new TreeSet<Simbolo>();
			peEstrella.add(terminal);
			getTablaPeEstrella().put(terminal, peEstrella);
		}
		
		//armo la tabla de relaciones
		Pares pares = armarPares();
		//System.out.println(pares.toString());
		//Para cada simbolo s que tiene al menos un adyacente
		TreeSet<Simbolo> simbolos = new TreeSet<Simbolo>();
		simbolos.addAll(gramatica.getNoTerminales());
		simbolos.addAll(gramatica.getTerminales());
		//Agrego el simbolo pesos
		simbolos.add(pesos);
		tabla = new Tabla(simbolos);
		AgregarSimbolos(pares);
		// Agrego a la tabla la relacion > si la gramatica acepta lambda.
		if(!gramatica.isNoProduccionesLambda()) {
			getTabla().addRelacion((Simbolo)gramatica.getSimboloInicial(),pesos, '>');
		}
		
		
		
	}		
	
	private void AgregarSimbolos(Pares pares) throws ConflictoTabla {
		for( Par par : pares.getPares()){
			// s =.y / adyacente(s,y) 	
			getTabla().addRelacion(par.getPrimero(), par.getSegundo(), '=');
			// s <. y / y pertenece P+(y)
			agregarPmas(par.getPrimero(), par.getSegundo());
			//U+(s) .> P*(V)
			agregarUmas(par.getPrimero(), par.getSegundo());
			}
		//		$<P+(Simbolo inicial)
		agregarRelacionesAPeso(getGramatica().getSimboloInicial());
	}
	
	private void agregarRelacionesAPeso(NoTerminal simboloInicial) throws ConflictoTabla {
		
		for(Simbolo y : getTablaPeMas().get(simboloInicial)){
			getTabla().addRelacion(getPesos(), y, '<');
		}
		for(Simbolo x : getTablaUMas().get(simboloInicial)){
			getTabla().addRelacion(x, getPesos(), '>');
		}
	}
	
	private void agregarUmas(Simbolo x ,Simbolo consecutivo) throws ConflictoTabla{
		for(Simbolo umas : getTablaUMas().get(x)){
			for (Simbolo ppor : getTablaPeEstrella().get(consecutivo)){
				getTabla().addRelacion(umas, ppor, '>');
			}
		}
	}
		
	private void agregarPmas(Simbolo x, Simbolo consecutivo) throws ConflictoTabla{
		
		for(Simbolo menor : getTablaPeMas().get(consecutivo)){
			getTabla().addRelacion(x, menor, '<');
		}
	}


	
// guardo los pares consecutivos de la gramatica
	private Pares armarPares(){
		
		Pares resultado = new Pares();
		
		for(Set<ParteDerecha> partesDerechas : getGramatica().getProducciones().values()){
			for(ParteDerecha derecha : partesDerechas){
				ArrayList<Simbolo> simbolosDer = derecha.getSimbolos();
				agregarPares(resultado, simbolosDer);							
			}
		}
		
		return resultado;
	}
	
	private void agregarPares(Pares pares, ArrayList<Simbolo> simbolos) {
		int posicionAnteUltimo = simbolos.size() - 1;
		for (int i = 0; i < posicionAnteUltimo; i++) {
			pares.agregarPar(new Par(simbolos.get(i).copy(), simbolos.get(i+1).copy()));
		}		
	}
	
	private void tablaExtremos() {
		Map<NoTerminal, Set<ParteDerecha>> producciones = gramatica.getProducciones();
		Set<ParteDerecha> simbolosDerecha;
		for (NoTerminal simboloNT : producciones.keySet()) {
			simbolosDerecha = producciones.get(simboloNT);

			Set<Simbolo> primerosDeSimbolo = new TreeSet<Simbolo>();
			Set<Simbolo> finalesDeSimbolo = new TreeSet<Simbolo>();
			for (ParteDerecha pd : simbolosDerecha) {
				primerosDeSimbolo.add(pd.primero());
				finalesDeSimbolo.add(pd.ultimo());
			}
			getComienzos().put(simboloNT, primerosDeSimbolo);
			getFinales().put(simboloNT, finalesDeSimbolo);
		}

	}

//calcularXMas(NoTerminal nt, tabla de extremos t)
//
//	resultado <- vacío
//	marcados <- vacío
//	pila <- vacía
//	pila.apilar(nt)
//	
//	mientras la pila tenga algo
//		observado <- pila.pop()
//		marcados.agregar(observado)
//		conjuntoObservado <- t.obtener(observado)
//		para cada simbolo s del conjuntoObservado
//			resultado.agregar(s)
//			si s es noTerminal y no está en marcados
//					pila.apilar(s)
//			fin si
//		fin para
//	fin mientras
//
//fin calcularXMas
	
	private Set<Simbolo> calcularXMas(NoTerminal noTerminal,
			Map<NoTerminal, Set<Simbolo>> tablaExtremos) {
		
		Set<Simbolo> resultado = new TreeSet<Simbolo>();
		
		Set<Simbolo> marcados = new	TreeSet<Simbolo>();
		Stack<NoTerminal> restantes = new Stack<NoTerminal>();
		marcados.add(noTerminal);
		restantes.push(noTerminal);
		
		while (!restantes.empty()) {
			NoTerminal actual = restantes.pop();
			marcados.add(actual);
			Set<Simbolo> extremosDelActual = tablaExtremos.get(actual);
			for(Simbolo s : extremosDelActual) {
				resultado.add(s.copy());
				if ((s instanceof NoTerminal) && (!marcados.contains(s))) {
					restantes.push((NoTerminal) s.copy());
				}
			}
		}
		
		return resultado;
	}
	
	private Set<Simbolo> calcularPeEstrella(Simbolo simbolo) {
		Set<Simbolo> resultado= new TreeSet<Simbolo>();
		if (simbolo instanceof NoTerminal){
			 resultado.addAll(getTablaPeMas().get(simbolo));
			 resultado.retainAll(getGramatica().getTerminales());
		}
		else
		{
			resultado.addAll(getTablaPeMas().get(simbolo));
			resultado.add(simbolo);
			resultado.retainAll(getGramatica().getTerminales());
		}
		return resultado;
	}

// Getters and Setters
	public void setTablaPeMas(Map<Simbolo, Set<Simbolo>> tablaPeMas) {
		this.tablaPeMas = tablaPeMas;
	}

	public Gramatica getGramatica() {
		return gramatica;
	}

	public void setGramatica(Gramatica gramatica) {
		this.gramatica = gramatica;
	}

	/*//Lo agrupe todo en armarTabla();
	 * private void generarPeMas(){ ArrayList<NoTerminal> noTerminales =
	 * gramatica.getNoTerminales();
	 * 
	 * for (NoTerminal simboloNT : noTerminales) {
	 * this.tablaPeMas.put(simboloNT, calcularPeMas(new TreeSet<Simbolo>(),
	 * simboloNT)); } }
	 */

	public Map<NoTerminal, Set<Simbolo>> getComienzos() {
		return comienzos;
	}

	public void setComienzos(Map<NoTerminal, Set<Simbolo>> comienzos) {
		this.comienzos = comienzos;
	}

	public Map<NoTerminal, Set<Simbolo>> getFinales() {
		return finales;
	}

	public void setFinales(Map<NoTerminal, Set<Simbolo>> finales) {
		this.finales = finales;
	}

	public Map<Simbolo, Set<Simbolo>> getTablaPeEstrella() {
		return tablaPeEstrella;
	}

	public void setTablaPeEstrella(Map<Simbolo, Set<Simbolo>> tablaPeEstrella) {
		this.tablaPeEstrella = tablaPeEstrella;
	}

	public Map<Simbolo, Set<Simbolo>> getTablaUMas() {
		return tablaUMas;
	}

	public void setTablaUMas(Map<Simbolo, Set<Simbolo>> tablaUMas) {
		this.tablaUMas = tablaUMas;
	}

	public Map<Simbolo, Set<Simbolo>> getTablaPeMas() {
		return tablaPeMas;
	}
	public Tabla getTabla() {
		return tabla;
	}
	public void setTabla(Tabla tabla) {
		this.tabla = tabla;
	}
	public Simbolo getPesos() {
		return pesos;
	}


	// para cada no terminal nt
	// definir calcularGPeMas(vacio, nt) a nt
	// fin para
	//
	// calcularGPeMas(marcados, nt)
	// resultado <- vacio
	// para cada s no terminal en comienzos(nt) tal que no esta en marcados
	// marcados.agregar(nt)
	// resultado.agregar(calcularGPeMas(marcados, s))
	// fin para
	// devolver resultado
	// fin calcularGPeMas
}
